package com.googlecode.recycled.lang.assertions;

import com.google.common.collect.Iterables;
import org.apache.commons.lang.StringUtils;

import javax.annotation.Nullable;
import java.util.Iterator;
import java.util.Map;

/**
 * Assertions utility.
 *
 * @author Henryk Konsek
 */
public class Assert {

    // CONSTRUCTION

    /**
     * Private constructor (since this class is intended to be used only as the utility).
     */
    private Assert() {
    }

    // PROTECTED HELPERS

    protected static String prepareMessage(String baseMessage, @Nullable Object messageContext) {
        String message = baseMessage;
        if (messageContext != null) {
            message += ": " + messageContext;
        } else {
            message += ".";
        }
        return message;
    }

    // NOT NULL METHODS

    public static <T> T notNull(T object, @Nullable Object messageContext) {
        if (object == null) {
            throw new NotNullAssertionException(prepareMessage("Non-null value expected", messageContext));
        }
        return object;
    }

    public static <T> T notNull(T object) {
        return notNull(object, null);
    }

    // NUMBER METHODS

    public static int assertGreaterThan(int actual, int expected) {
        if (actual <= expected) {
            throw new IllegalSizeAssertionException(prepareMessage("Value should be larger than " + expected, actual));
        }
        return actual;
    }

    public static int assertLesserThan(int actual, int expected) {
        if (actual >= expected) {
            throw new IllegalSizeAssertionException(prepareMessage("Value should be larger than " + expected, actual));
        }
        return actual;
    }

    public static int assertPositive(int actual) {
        return assertGreaterThan(actual, 0);
    }

    public static int assertNegative(int actual) {
        return assertLesserThan(actual, 0);
    }

    // STRING METHODS

    public static String notBlank(String string, @Nullable Object messageContext) {
        if (StringUtils.isBlank(string)) {
            throw new NotEmptyAssertionException(prepareMessage("Non-blank String expected", messageContext));
        }
        return string;
    }

    public static String notBlank(String string) {
        return notBlank(string, null);
    }

    // ITERABLES SIZE METHODS

    public static <T> Iterable<T> notEmpty(Iterable<T> iterable, @Nullable Object messageContext) {
        if (Iterables.isEmpty(notNull(iterable, messageContext))) {
            throw new NotEmptyAssertionException(prepareMessage("Non-empty iterable expected", messageContext));
        }
        return iterable;
    }

    public static <T> Iterable<T> notEmpty(Iterable<T> iterable) {
        return notEmpty(iterable, null);
    }

    public static <T> T single(Iterable<T> iterable, @Nullable Object messageContext) {
        Iterator<T> iterator = notEmpty(iterable, messageContext).iterator();
        T singletonValue = iterator.next();
        if (iterator.hasNext()) {
            throw new IllegalSizeAssertionException(prepareMessage("Singleton expected", messageContext));
        }
        return singletonValue;
    }

    public static <T> T single(Iterable<T> iterable) {
        return single(iterable, null);
    }

    // ARRAYS SIZE METHODS

    public static <T> T[] notEmpty(T[] array, @Nullable Object messageContext) {
        if (notNull(array, messageContext).length == 0) {
            throw new NotEmptyAssertionException(prepareMessage("Non-empty array expected", messageContext));
        }
        return array;
    }

    public static <T> T[] notEmpty(T[] array) {
        return notEmpty(array, null);
    }

    public static <T> T[] size(T[] array, int size, @Nullable Object messageContext) {
        if (notEmpty(array, messageContext).length != size) {
            throw new IllegalSizeAssertionException(prepareMessage("Size " + size + " expected, found " + array.length, messageContext));
        }
        return array;
    }

    public static <T> T[] size(T[] array, int size) {
        return size(array, size, null);
    }

    public static <T> T single(T[] array, @Nullable Object messageContext) {
        return size(array, 1, messageContext)[0];
    }

    public static <T> T single(T[] array) {
        return size(array, 1)[0];
    }

    // PRIMITIVE ARRAYS SIZE METHODS

    public static byte[] notEmpty(byte[] array, @Nullable Object messageContext) {
        if (notNull(array, messageContext).length == 0) {
            throw new NotEmptyAssertionException(prepareMessage("Non-empty array expected", messageContext));
        }
        return array;
    }

    public static byte[] notEmpty(byte[] bytes) {
        return notEmpty(bytes, null);
    }

    // MAPS SIZE METHODS

    public static <K, V> Map<K, V> notEmpty(Map<K, V> map, @Nullable Object messageContext) {
        if (notNull(map, messageContext).isEmpty()) {
            throw new NotEmptyAssertionException(prepareMessage("Non-empty map expected", messageContext));
        }
        return map;
    }

    public static <K, V> Map<K, V> notEmpty(Map<K, V> map) {
        return notEmpty(map, null);
    }

    public static <K, V> Map<K, V> size(Map<K, V> map, int size, @Nullable Object messageContext) {
        if (notEmpty(map, messageContext).size() != size) {
            throw new IllegalSizeAssertionException(prepareMessage("Size " + size + " expected, found " + map.size(), messageContext));
        }
        return map;
    }

    public static <K, V> Map<K, V> size(Map<K, V> map, int size) {
        return size(map, size, null);
    }

    public static <K, V> Map.Entry<K, V> single(Map<K, V> map, @Nullable Object messageContext) {
        return size(map, 1, messageContext).entrySet().iterator().next();
    }

    public static <K, V> Map.Entry<K, V> single(Map<K, V> map) {
        return size(map, 1).entrySet().iterator().next();
    }

}
